Resumen

Un árbol de decisión es una herramienta de apoyo a la decisión que utiliza un modelo en forma de árbol de decisiones y sus posibles consecuencias, incluyendo los resultados resultados, costes de recursos y utilidad. Es una forma de mostrar un algoritmo que sólo contiene declaraciones de control condicional.

Ejemplo sencillo

Tenemos las calificaciones de un examen de 10 alumnos por medio de la variable score, luego las horas que dedican de estudio en hours y sus calificaciones promedio midterm. Nos preguntamos cuáles son los estudintes que tienen mejores calificaciones.

Podemos realizar un árbol de decisión simple clasificando los estudiantes por medio de las variables. ¿Cómo realizamos este árbol?

  1. Calculamos la calificación promedio de los 10 estudiantes: 57 y tenemos el 100% de la muestra,

  2. Nos preguntamos, ¿Estudia menos de 10 horas?, si la respuesta es sí, demos que tenemos 5 registros, que corresponden al 50% de los datos, y su calificación promedio es de 39; por otro lado, si la respuesta es no tenemos que su calificación promedio fue de 75.

  3. Con este último grupo, nos hacemos la pregunta, ¿su calificación promedio es menor que 65?, si la respuesta es sí, tenemos 3 estudiantes, que corresponden al 30% de todos los datos, con calificación promedio 70 y si la respuesta es no, tenemos 2 estudaintes, que corresponde al 20% de todos los datos, con califiacción promedio de 82.

score hours midterm
1 35 6 42
2 38 5 65
3 40 7 35
4 45 6 75
5 35 8 60
6 65 11 50
7 70 12 45
8 75 18 40
9 80 14 80
10 85 12 82
  1. Dividimos el espacio del predictor, es decir, el conjunto de valores posibles para \(X_1,X_2,...,X_p\), en \(J\) regiones distintas y no superpuestas, \(R_1,R_2,...,R_J\).

  2. Para cada observación que cae en la región \(R_j\), hacemos la misma predicción, que es simplemente la media de los valores de respuesta para las observaciones de entrenamiento en \(R_j\).

El objetivo es minimizar el RSS (Residual Sum of Squares):

\[ \sum_{j=1}^J\sum_{i\in R_j} (y_i-\hat{y}_{R_j})^2 \]

Debemos tener algunas consideraciones de estos árboles:

  • Enfoque descendente y codicioso que se conoce como división binaria recursiva.

  • Es descendente porque comienza en la parte superior del árbol y luego divide sucesivamente el espacio de predicción

  • Cada división se indica mediante dos nuevas ramas más abajo en el árbol.

  • Es codicioso porque en cada paso del proceso de construcción del árbol, se realiza la mejor división en ese paso en particular, en lugar de mirar hacia adelante y elegir una división que conduzca a un mejor árbol en algún paso futuro.

En resumen podemos seguir los siguientes pasos:

  1. Considera todos los predictores y todos los posibles valores del punto de corte

  2. Calcula el RSS para cada posibilidad

  3. Selecciona la que tiene menos RSS

  4. Continúa hasta que se alcanzan los criterios de parada

Tipos de árboles de decisión

  1. Árbol de regresión: Para la variable objetivo cuantitativa continua. Por ejemplo, predicción de precipitaciones, predicción de ingresos, predicción de notas, etc.

  2. Árbol de clasificación: Para variables objetivo categóricas discretas. Ej. Predicción de alto o bajo, victoria o pérdida, saludable o no saludable, etc.

  1. Dividimos el espacio del predictor, es decir, el conjunto de valores posibles para \(X_1,X_2,...,X_p\), en \(J\) regiones distintas y no superpuestas, \(R_1,R_2,...,R_J\).

  2. Para cada observación que cae en la región \(R_j\), hacemos la misma predicción, que es simplemente la media de los valores de respuesta para las observaciones de entrenamiento en \(R_j\).

El objetivo es minimizar el RSS (Residual Sum of Squares):

\[ \sum_{j=1}^J\sum_{i\in R_j} (y_i-\hat{y}_{R_j})^2 \]

Debemos tener algunas consideraciones de estos árboles:

  • Enfoque descendente y codicioso que se conoce como división binaria recursiva.

  • Es descendente porque comienza en la parte superior del árbol y luego divide sucesivamente el espacio de predicción

  • Cada división se indica mediante dos nuevas ramas más abajo en el árbol.

  • Es codicioso porque en cada paso del proceso de construcción del árbol, se realiza la mejor división en ese paso en particular, en lugar de mirar hacia adelante y elegir una división que conduzca a un mejor árbol en algún paso futuro.

En resumen podemos seguir los siguientes pasos:

  1. Considera todos los predictores y todos los posibles valores del punto de corte

  2. Calcula el RSS para cada posibilidad

  3. Selecciona la que tiene menos RSS

  4. Continúa hasta que se alcanzan los criterios de parada

Tipos de árboles de decisión

  1. Árbol de regresión: Para la variable objetivo cuantitativa continua. Por ejemplo, predicción de precipitaciones, predicción de ingresos, predicción de notas, etc.

  2. Árbol de clasificación: Para variables objetivo categóricas discretas. Ej. Predicción de alto o bajo, victoria o pérdida, saludable o no saludable, etc.

Criterios de parada

Podemos tener distintos criterios, tales como:

  1. Observaciones mínimas en un nodo. Número mínimo de observaciones necesarias para una nueva división.

  2. Observaciones mínimas en el nodo de la hoja. Número mínimo de observaciones necesarias en cada nodo después de la división.

  3. Profundidad máxima. Número máximo de capas del árbol posibles

Construyamos un árbol de decisión en R

Datos para el ejemplo

Para ver cómo construimos un árbol, primero que todo vamos a ver nuestros datos:

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.2     ✓ dplyr   1.0.7
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
── Conflicts ─────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
movie <- read.csv("Movie_regression.csv")
head(movie)
colnames(movie)
 [1] "Marketing.expense"   "Production.expense" 
 [3] "Multiplex.coverage"  "Budget"             
 [5] "Movie_length"        "Lead_.Actor_Rating" 
 [7] "Lead_Actress_rating" "Director_rating"    
 [9] "Producer_rating"     "Critic_rating"      
[11] "Trailer_views"       "X3D_available"      
[13] "Time_taken"          "Twitter_hastags"    
[15] "Genre"               "Avg_age_actors"     
[17] "Num_multiplex"       "Collection"         

Recordemos un poco de exploración de datos usando el paquete DataExplorer:

library(DataExplorer)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
plot_intro(movie)

plot_missing(movie)

En esta primera parte podemos ver cómo los datos que vamos a analizar no tienen problemas de datos faltantes, para lo cual pusieramos eliminar alguna de las variables.

Para mirar un poco de qué tipos de variables tenemos en nuestro dataset hagamos un resumen de cada una de las variables:

summary(movie)
 Marketing.expense Production.expense Multiplex.coverage
 Min.   :  20.13   Min.   : 55.92     Min.   :0.1290    
 1st Qu.:  21.64   1st Qu.: 65.38     1st Qu.:0.3760    
 Median :  25.13   Median : 74.38     Median :0.4620    
 Mean   :  92.27   Mean   : 77.27     Mean   :0.4453    
 3rd Qu.:  93.54   3rd Qu.: 91.20     3rd Qu.:0.5510    
 Max.   :1799.52   Max.   :110.48     Max.   :0.6150    
                                                        
     Budget       Movie_length   Lead_.Actor_Rating
 Min.   :19781   Min.   : 76.4   Min.   :3.840     
 1st Qu.:32694   1st Qu.:118.5   1st Qu.:7.316     
 Median :34488   Median :151.0   Median :8.307     
 Mean   :34911   Mean   :142.1   Mean   :8.014     
 3rd Qu.:36794   3rd Qu.:167.6   3rd Qu.:8.865     
 Max.   :48773   Max.   :173.5   Max.   :9.435     
                                                   
 Lead_Actress_rating Director_rating Producer_rating Critic_rating  
 Min.   :4.035       Min.   :3.840   Min.   :4.030   Min.   :6.600  
 1st Qu.:7.504       1st Qu.:7.296   1st Qu.:7.508   1st Qu.:7.200  
 Median :8.495       Median :8.312   Median :8.465   Median :7.960  
 Mean   :8.186       Mean   :8.020   Mean   :8.191   Mean   :7.811  
 3rd Qu.:9.030       3rd Qu.:8.884   3rd Qu.:9.030   3rd Qu.:8.260  
 Max.   :9.540       Max.   :9.425   Max.   :9.635   Max.   :9.400  
                                                                    
 Trailer_views    X3D_available        Time_taken   
 Min.   :212912   Length:506         Min.   :  0.0  
 1st Qu.:409128   Class :character   1st Qu.:132.3  
 Median :462460   Mode  :character   Median :160.0  
 Mean   :449861                      Mean   :157.4  
 3rd Qu.:500248                      3rd Qu.:181.9  
 Max.   :567784                      Max.   :217.5  
                                     NA's   :12     
 Twitter_hastags     Genre           Avg_age_actors  Num_multiplex  
 Min.   : 201.2   Length:506         Min.   : 3.00   Min.   :333.0  
 1st Qu.: 223.8   Class :character   1st Qu.:28.00   1st Qu.:465.0  
 Median : 254.4   Mode  :character   Median :39.00   Median :535.5  
 Mean   : 260.8                      Mean   :39.18   Mean   :545.0  
 3rd Qu.: 283.4                      3rd Qu.:50.00   3rd Qu.:614.8  
 Max.   :2022.4                      Max.   :60.00   Max.   :868.0  
                                                                    
   Collection    
 Min.   : 10000  
 1st Qu.: 34050  
 Median : 42400  
 Mean   : 45058  
 3rd Qu.: 50000  
 Max.   :100000  
                 

Construcción de conjuntos de datos test-train

Para realizar esta partición, utilizaremos otra metología usando el paquete caTools, por lo cual primero debemos de instalarlo en R por medio del comando install.packages("caTools"). Ya teniendo la instalación procedemos a cargar la librería y a realizar nuesta partición:

library(caTools)

set.seed(0)  # La semilla
split = sample.split(movie,SplitRatio=0.8) # Los datos de train serán de 80%
train = subset(movie,split == TRUE)
test = subset(movie,split == FALSE)

La variable de control para enconetrar el árbol de decisión es Collection, para lo cual, recordemos que es importante ver que esta variable se distribuya de forma similar en ambos conjuntos de train y test:

summary(train$Collection)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  10000   34400   42400   45247   50000  100000 
summary(test$Collection)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  11200   32200   42400   44398   50600  100000 

Construcción del árbol de decisión para problema de regresión

Para la construcción de nuestro árbol de decisión para un problema de regresión, nos apoyaremos de los paquete de R: rpart, rpart.plot.

Lo primero que haremos es cargar las dos librerías luego de haber instalado previamente los paquetes:

library(rpart)
library(rpart.plot)

Para este ejemplo, utilizaremos el modelo de regresión que se encuentra en esta paquetería, donde nuestra variable de interés o variable objetivo será Colection. El criterio de parada lo tomaremos por medio de la función rpart.control, donde se especifica que maxdepth = 3, esto establece que, la profundidad máxima de un nodo final del árbol, contando desde el nodo raíz es máximo 3.

# Modelo de regresión con el conjunto de datos train
regtree <- rpart(formula=Collection~., 
                 data = train, 
                 control = rpart.control(maxdepth =  3))

# Visualización del árbol de decisión
rpart.plot(regtree, box.palette = "RdBu", digits= -3)

# Cálculo del predictor
test <- test %>%  mutate(pred =predict(regtree, test, type = "vector"),
                         reg = 1:nrow(test),
                         dif_pred_coll = Collection-pred)

library(ggplot2)

ggplot(test)+
  geom_point(aes(x=reg, y=dif_pred_coll))


MSE2 <- mean((test$pred - test$Collection)^2)

Poda de un árbol de decisión

Los grandes árboles de decisión (con muchos nodos y muchas divisiones), tienen dos problemas: la primera es que son difíciles de interpretar y la segunda es que sobrealimentan los datos del tren dando así un mal rendimiento de los activos. Una de las formas de controlar esto, es como ya lo hemos estado haciendo: por medio de los criterios de parada.

Otra estrategia que podemos tener es hacer un árbol muy grande, y luego podarlo o cortar algunas partes que no nos benefician en el cálculo del objetivo.

Así que ahora queremos un subárbol con es la prueba más baja a una tasa podemos utilizar la validación cruzada para averiguar la tasa de error de la prueba de todos los subsidios de este tipo, pero esto puede ser computacionalmente caro (va a tardar mucho tiempo).

Por lo tanto, utilizamos un método llamado “poda de complejidad de costes” o “poda de eslabones más despiertos” en la materia.

Añadimos el coste adicional del número de nodos terminales en el árbol al RSS, de modo que en lugar de minimizar las direcciones, minimizamos el RSS más el número de nodos terminales, lo que se denomina parámetro único o parámetro de complejidad.

\[ \sum_{m=1}^{|T|}\sum_{x_i\in R_m}(y_i-\hat{y}_{R_m})^2+\alpha|T| \]

donde \(\alpha\) es el parámetro de complejidad, \(T\) es el número de hojas del árbol.

Estamos minimizando el sistema de orden normal por lo que obtenemos raíces normales del árbol pero el valor de la multa aumenta que es el aumento de la pena por tener más división.

Por lo tanto, el valor de \(\alpha\) controla el crecimiento del árbol, así que el proceso es el siguiente: tenemos que encontrar el valor de \(\alpha\) en el que obtenemos el mínimo error con validación cruzada en nuestro conjunto de entrenamiento, utilizando el valor de \(\alpha\) para podar el árbol y esperamos que este árbol podado se desempeñe mejor en el conjunto de prueba.

Hagamos este procedimiento siguiendo el ejemplo que anterior de las películas en R. Primero hagamos el árbol completo (con todas las posibilidades), el cual llamaremos fulltree y donde cp es control parameter .

# Tree Pruning
fulltree <- rpart(formula=Collection~., 
                 data = train, 
                 control = rpart.control(cp = 0))

# Visualización del árbol de decisión
rpart.plot(fulltree, box.palette = "RdBu", digits= -3)

Podemos ver el comportamiento de éste parámetro de control por medio la función printcp()

printcp(fulltree)

Regression tree:
rpart(formula = Collection ~ ., data = train, control = rpart.control(cp = 0))

Variables actually used in tree construction:
 [1] Avg_age_actors      Budget              Critic_rating      
 [4] Director_rating     Genre               Lead_.Actor_Rating 
 [7] Lead_Actress_rating Marketing.expense   Movie_length       
[10] Multiplex.coverage  Production.expense  Time_taken         
[13] Trailer_views       Twitter_hastags     X3D_available      

Root node error: 1.3086e+11/393 = 332989821

n= 393 

           CP nsplit rel error  xerror     xstd
1  0.43507653      0   1.00000 1.00273 0.095500
2  0.15658756      1   0.56492 0.58950 0.066680
3  0.09064716      2   0.40834 0.43374 0.054116
4  0.04887218      3   0.31769 0.34122 0.048513
5  0.03105207      4   0.26882 0.36855 0.053490
6  0.02172378      5   0.23776 0.30922 0.046400
7  0.01458513      6   0.21604 0.27922 0.042112
8  0.00977123      7   0.20146 0.28362 0.041988
9  0.00935818      8   0.19168 0.28225 0.041985
10 0.00808528      9   0.18233 0.29373 0.042983
11 0.00793019     10   0.17424 0.29301 0.042983
12 0.00548094     11   0.16631 0.27704 0.041450
13 0.00507787     12   0.16083 0.26325 0.040938
14 0.00476304     13   0.15575 0.26155 0.040922
15 0.00331305     14   0.15099 0.25770 0.040956
16 0.00291437     15   0.14768 0.25791 0.040942
17 0.00263491     16   0.14476 0.25580 0.040943
18 0.00200138     17   0.14213 0.25596 0.041052
19 0.00196853     18   0.14013 0.25447 0.041500
20 0.00153874     19   0.13816 0.25376 0.041501
21 0.00135750     20   0.13662 0.25523 0.041490
22 0.00130090     21   0.13526 0.25438 0.041492
23 0.00122409     22   0.13396 0.25314 0.041493
24 0.00084140     23   0.13274 0.25129 0.041516
25 0.00075770     24   0.13189 0.25177 0.041500
26 0.00074873     25   0.13114 0.25154 0.041487
27 0.00073565     27   0.12964 0.25139 0.041488
28 0.00067572     28   0.12890 0.25220 0.041481
29 0.00047007     29   0.12823 0.25244 0.041454
30 0.00039681     30   0.12776 0.25213 0.041464
31 0.00034292     31   0.12736 0.25272 0.041469
32 0.00028553     32   0.12702 0.25263 0.041472
33 0.00000000     33   0.12673 0.25239 0.041472

Aquí puedes ver los diferentes valores de CP para los cuales obtenemos diferentes valores de error relativo. El valor de xerror es el error de validación cruzada.

Queremos encontrar ese valor de CP para el cual la edición de validación cruzada es mínima. Ese valor de CP otro parámetro de ajuste se utilizará para podar el árbol.

Ahora también se puede graficar estos valores de CP y exeter usando esta función de plotcp y se puede ver que a medida que seguimos incrementando el valor de CP el error relativo está inicialmente disminuyendo y luego comienza a aumentar y así sucesivamente. Por lo tanto, como sube e baja este valor, en algún lugar de habar un valor para CP para el cual es mínimo (mínimo global).

plotcp(fulltree)

Encontremos el mínimo calor de CP. con la siguiente línea vamos a encontrar el valor de CP para el cual es el error de validación cruzada xerror es mínimo.

mincp <- regtree$cptable[which.min(regtree$cptable[,"xerror"]),"CP"]
mincp
[1] 0.01458513

Usando este parámetro que encontramos como mínimo, ejecutaremos la función prune() y el mismo árbol será podado.

prunedtree <- prune(fulltree, cp = mincp)
rpart.plot(prunedtree, box.palette = "RdBu", digits = 3)

Podemos comparar los resultados con el árbol sin podar y el árbol podado, para esto hacemos el cálculo, tal como lo hicimos en el ejemplo anterior, antes de podar:

test$fulltree <- predict(fulltree, test, type = "vector")
MSE2 <- mean((test$fulltree - test$Collection)^2)
MSE2
[1] 99037642
test$pruned <- predict(prunedtree, test, type = "vector")
MSE2pruned <- mean((test$pruned - test$Collection)^2)
MSE2pruned
[1] 93731913

Construyamos un árbol de clasificación

Para los árboles de clasificación, utilizamos el modo, donde, la categoría más frecuente en esa región será la predicción.

Tanto la regresión como la clasificación utilizan la división binaria recursiva

En la regresión se utiliza el RSS para decidir la división

En la clasificación podemos utilizar

  1. Tasa de error de clasificación

  2. Índice de Gini

  3. Entropía cruzada

Ahora para verlo en un ejemplo utilizaremos el mismo dataset de las películas, solo que ahora queremos ver clasificar las películas de acuerdo si ganarán un Oscar por medio de la variable binaria Star_Tech_Oscar

Ahora veamos cómo realizamos un árbol de clasificación desde R.

mc <- read.csv("Movie_classification.csv")
head(mc)
summary(mc)
 Marketing.expense Production.expense Multiplex.coverage
 Min.   :  20.13   Min.   : 55.92     Min.   :0.1290    
 1st Qu.:  21.64   1st Qu.: 65.38     1st Qu.:0.3760    
 Median :  25.13   Median : 74.38     Median :0.4620    
 Mean   :  92.27   Mean   : 77.27     Mean   :0.4453    
 3rd Qu.:  93.54   3rd Qu.: 91.20     3rd Qu.:0.5510    
 Max.   :1799.52   Max.   :110.48     Max.   :0.6150    
                                                        
     Budget       Movie_length   Lead_.Actor_Rating
 Min.   :19781   Min.   : 76.4   Min.   :3.840     
 1st Qu.:32694   1st Qu.:118.5   1st Qu.:7.316     
 Median :34488   Median :151.0   Median :8.307     
 Mean   :34911   Mean   :142.1   Mean   :8.014     
 3rd Qu.:36794   3rd Qu.:167.6   3rd Qu.:8.865     
 Max.   :48773   Max.   :173.5   Max.   :9.435     
                                                   
 Lead_Actress_rating Director_rating Producer_rating Critic_rating  
 Min.   :4.035       Min.   :3.840   Min.   :4.030   Min.   :6.600  
 1st Qu.:7.504       1st Qu.:7.296   1st Qu.:7.508   1st Qu.:7.200  
 Median :8.495       Median :8.312   Median :8.465   Median :7.960  
 Mean   :8.186       Mean   :8.020   Mean   :8.191   Mean   :7.811  
 3rd Qu.:9.030       3rd Qu.:8.884   3rd Qu.:9.030   3rd Qu.:8.260  
 Max.   :9.540       Max.   :9.425   Max.   :9.635   Max.   :9.400  
                                                                    
 Trailer_views    X3D_available        Time_taken   
 Min.   :212912   Length:506         Min.   :  0.0  
 1st Qu.:409128   Class :character   1st Qu.:132.3  
 Median :462460   Mode  :character   Median :160.0  
 Mean   :449861                      Mean   :157.4  
 3rd Qu.:500248                      3rd Qu.:181.9  
 Max.   :567784                      Max.   :217.5  
                                     NA's   :12     
 Twitter_hastags     Genre           Avg_age_actors  Num_multiplex  
 Min.   : 201.2   Length:506         Min.   : 3.00   Min.   :333.0  
 1st Qu.: 223.8   Class :character   1st Qu.:28.00   1st Qu.:465.0  
 Median : 254.4   Mode  :character   Median :39.00   Median :535.5  
 Mean   : 260.8                      Mean   :39.18   Mean   :545.0  
 3rd Qu.: 283.4                      3rd Qu.:50.00   3rd Qu.:614.8  
 Max.   :2022.4                      Max.   :60.00   Max.   :868.0  
                                                                    
   Collection     Start_Tech_Oscar
 Min.   : 10000   Min.   :0.0000  
 1st Qu.: 34050   1st Qu.:0.0000  
 Median : 42400   Median :1.0000  
 Mean   : 45058   Mean   :0.5455  
 3rd Qu.: 50000   3rd Qu.:1.0000  
 Max.   :100000   Max.   :1.0000  
                                  

Podemos ver que la variable Time_taken tiene valores en NA, esto nos va a generar problemas en el momento que realicemos las corridas de cualquier modelo en R. Para eliminar estos NA realizamos el siguiente procedimiento.

mc$Time_taken[is.na(mc$Time_taken)] <- mean(mc$Time_taken,na.rm = TRUE)

Ya que hemos limpiado los datos, procedemos a realizar la separación de nuestros conjuntos Test y Train, sin olvidar que si, no hemos ejecutado la librería caTools debemos de hacerlo por medio de l comando library(caTools):

set.seed(0)
split <- sample.split(mc,SplitRatio = 0.8)
trainc <- subset(mc,split == TRUE)
testc <- subset(mc,split == FALSE)

Ahora, utilizando el paquete de rpart, vamos a encontrar el árbol de clasificación, cambiando el método por class.

# Modelo de clasificación
classtree <- rpart(formula = Start_Tech_Oscar~.,
                   data = trainc,
                   method = "class",
                   control = rpart.control(maxdepth = 3))

rpart.plot(classtree, box.palette="RdBu", digits = -3)

De igual forma, podemos buscar el predictor, evaluando en nuestro modelo en los datos de test:

testc$pred <- predict(classtree, testc, type="class") 
testc %>% select(Start_Tech_Oscar,
                 pred)

Podemos ver y comparar cómo está clasificando nuestro modelo y vemos que solo en esta simple visualización no tenemos todos los datos de clasificados de forma correcta.

Para comparar como se está comportando en forma general nuestro modelos de clasificación por medio de un árbol de decisión, una forma es comparar gráficamente los valores actuales junto con los que se predicen.

Hagamos una comparación creando una tabla:

table(testc$Start_Tech_Oscar,testc$pred)
   
     0  1
  0 45  5
  1 38 19

Esta matriz (la famosa matriz de confusión), lo que nos indica es que para el primer caso, la diagonal nos mostrará el número de valores que se predijeron correctamente. Por ejemplo las películas que no tuvieron Oscar (Start_Tech_Oscar== 0), el modelo encontró 45 correctas de 50 (45+5), mientras que para las películas que tuvieron Oscar, el modelo encontró 19 correctas de 57 (38+19).

Así que cuando la película es realmente ganar el Oscar tenemos una muy mala precisión de predicción (accuracy). Mientras que cuando la película no gana el Oscar tenemos una muy buena precisión de predicción.

Si queremos saber cuanto exactamente es nuestro accuracy en % podemos realizarlo por medio la división: los que acertó/todos:

(45+19)/(45+5+38+19)*100
[1] 59.81308

Referencias

LS0tCnRpdGxlOiAiQmxvcXVlIDI6IEVqZW1wbG9zIHkgYXBsaWNhY2lvbmVzIGRlIMOhcmJvbGVzIGRlIGRlY2lzacOzbiIKc3VidGl0bGU6ICJBbmFsw610aWNhIGJhc2FkYSBlbiDDoXJib2xlcyBkZSBjbGFzaWZpY2FjacOzbiB5IHJlZ3Jlc2nDs24iCmF1dGhvcjogIlByb2Zlc29yYTogRHJhLiBEaWFuYSBQYW9sYSBNb250b3lhIEVzY29iYXIgZGlhbmEubW9udG95YUBpdGVzby5teCIKZGF0ZTogIk1hcnpvIDIwMjMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0aGVtZTogY29zbW8KICAgIGhpZ2hsaWdodDogdGFuZ28KICBnaXRodWJfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGV2OiBqcGVnCi0tLQpgYGB7ciBzZXR1cCwgZWNobyA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA3KQpgYGAKCjxzdHlsZT4KLmZvcmNlQnJlYWsgeyAtd2Via2l0LWNvbHVtbi1icmVhay1hZnRlcjogYWx3YXlzOyBicmVhay1hZnRlcjogY29sdW1uOyB9Cjwvc3R5bGU+Cgo8Y2VudGVyPgohW10oLi9pbWFnZXMvZHJ5X3RyZWUucG5nKXt3aWR0aD0xMCV9CiFbXSguL2ltYWdlcy9pdGVzby5qcGVnKXt3aWR0aD01JX0KPC9jZW50ZXI+CgojIFJlc3VtZW4KClVuIMOhcmJvbCBkZSBkZWNpc2nDs24gZXMgdW5hIGhlcnJhbWllbnRhIGRlIGFwb3lvIGEgbGEgZGVjaXNpw7NuIHF1ZSB1dGlsaXphIHVuIG1vZGVsbyBlbiBmb3JtYSBkZSDDoXJib2wgZGUgZGVjaXNpb25lcyB5IHN1cyBwb3NpYmxlcyBjb25zZWN1ZW5jaWFzLCBpbmNsdXllbmRvIGxvcyByZXN1bHRhZG9zIHJlc3VsdGFkb3MsIGNvc3RlcyBkZSByZWN1cnNvcyB5IHV0aWxpZGFkLiBFcyB1bmEgZm9ybWEgZGUgbW9zdHJhciB1biBhbGdvcml0bW8gcXVlIHPDs2xvIGNvbnRpZW5lIGRlY2xhcmFjaW9uZXMgZGUgY29udHJvbCBjb25kaWNpb25hbC4gCgo8Y2VudGVyPgohW10oLi9pbWFnZXMvdHJlZTEucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKIyMgRWplbXBsbyBzZW5jaWxsbwoKVGVuZW1vcyBsYXMgY2FsaWZpY2FjaW9uZXMgZGUgdW4gZXhhbWVuIGRlIDEwIGFsdW1ub3MgcG9yIG1lZGlvIGRlIGxhIHZhcmlhYmxlIGBzY29yZWAsIGx1ZWdvIGxhcyBob3JhcyBxdWUgZGVkaWNhbiAgZGUgZXN0dWRpbyBlbiBgaG91cnNgIHkgc3VzIGNhbGlmaWNhY2lvbmVzIHByb21lZGlvIGBtaWR0ZXJtYC4gTm9zIHByZWd1bnRhbW9zIGN1w6FsZXMgc29uIGxvcyBlc3R1ZGludGVzIHF1ZSB0aWVuZW4gbWVqb3JlcyBjYWxpZmljYWNpb25lcy4KClBvZGVtb3MgcmVhbGl6YXIgdW4gw6FyYm9sIGRlIGRlY2lzacOzbiBzaW1wbGUgY2xhc2lmaWNhbmRvIGxvcyBlc3R1ZGlhbnRlcyBwb3IgbWVkaW8gZGUgbGFzIHZhcmlhYmxlcy4gwr9Dw7NtbyByZWFsaXphbW9zIGVzdGUgw6FyYm9sPwoKMS4gQ2FsY3VsYW1vcyBsYSBjYWxpZmljYWNpw7NuIHByb21lZGlvIGRlIGxvcyAxMCBlc3R1ZGlhbnRlczogNTcgeSB0ZW5lbW9zIGVsIDEwMCUgZGUgbGEgbXVlc3RyYSwKCjIuIE5vcyBwcmVndW50YW1vcywgwr9Fc3R1ZGlhIG1lbm9zIGRlIDEwIGhvcmFzPywgc2kgbGEgcmVzcHVlc3RhIGVzIHPDrSwgZGVtb3MgcXVlIHRlbmVtb3MgNSByZWdpc3Ryb3MsIHF1ZSBjb3JyZXNwb25kZW4gYWwgNTAlIGRlIGxvcyBkYXRvcywgeSBzdSBjYWxpZmljYWNpw7NuIHByb21lZGlvIGVzIGRlIDM5OyBwb3Igb3RybyBsYWRvLCBzaSBsYSByZXNwdWVzdGEgZXMgbm8gdGVuZW1vcyBxdWUgc3UgY2FsaWZpY2FjacOzbiBwcm9tZWRpbyBmdWUgZGUgNzUuIAoKMy4gQ29uIGVzdGUgw7psdGltbyBncnVwbywgbm9zIGhhY2Vtb3MgbGEgcHJlZ3VudGEsIMK/c3UgY2FsaWZpY2FjacOzbiBwcm9tZWRpbyBlcyBtZW5vciBxdWUgNjU/LCAgc2kgbGEgcmVzcHVlc3RhIGVzIHPDrSwgdGVuZW1vcyAzIGVzdHVkaWFudGVzLCBxdWUgY29ycmVzcG9uZGVuIGFsIDMwJSBkZSB0b2RvcyBsb3MgZGF0b3MsIGNvbiBjYWxpZmljYWNpw7NuIHByb21lZGlvIDcwIHkgc2kgbGEgcmVzcHVlc3RhIGVzIG5vLCB0ZW5lbW9zIDIgZXN0dWRhaW50ZXMsIHF1ZSBjb3JyZXNwb25kZSBhbCAyMCUgZGUgdG9kb3MgbG9zIGRhdG9zLCBjb24gY2FsaWZpYWNjacOzbiBwcm9tZWRpbyBkZSA4Mi4KCgp8ICB8IHNjb3JlIHwgaG91cnMgfCBtaWR0ZXJtIHwKfC0tfC0tLS0tLS18LS0tLS0tLXwtLS0tLS0tLS18CnwxIHwgIDM1ICAgfCAgNiAgICB8ICAgIDQyICAgfAp8MiB8ICAzOCAgIHwgIDUgICAgfCAgICA2NSAgIHwKfDMgfCAgNDAgICB8ICA3ICAgIHwgICAgMzUgICB8Cnw0IHwgIDQ1ICAgfCAgNiAgICB8ICAgIDc1ICAgfAp8NSB8ICAzNSAgIHwgIDggICAgfCAgICA2MCAgIHwKfDYgfCAgNjUgICB8ICAxMSAgIHwgICAgNTAgICB8Cnw3IHwgIDcwICAgfCAgMTIgICB8ICAgIDQ1ICAgfAp8OCB8ICA3NSAgIHwgIDE4ICAgfCAgICA0MCAgIHwKfDkgfCAgODAgICB8ICAxNCAgIHwgICAgODAgICB8CnwxMHwgIDg1ICAgfCAgMTIgICB8ICAgIDgyICAgfAoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL3RyZWUyLnBuZyl7d2lkdGg9NDAlfQo8L2NlbnRlcj4KCjEuIERpdmlkaW1vcyBlbCBlc3BhY2lvIGRlbCBwcmVkaWN0b3IsIGVzIGRlY2lyLCBlbCBjb25qdW50byBkZSB2YWxvcmVzIHBvc2libGVzIHBhcmEgJFhfMSxYXzIsLi4uLFhfcCQsIGVuICRKJCByZWdpb25lcyBkaXN0aW50YXMgeSBubyBzdXBlcnB1ZXN0YXMsICRSXzEsUl8yLC4uLixSX0okLgoKMi4gUGFyYSBjYWRhIG9ic2VydmFjacOzbiBxdWUgY2FlIGVuIGxhIHJlZ2nDs24gJFJfaiQsIGhhY2Vtb3MgbGEgbWlzbWEgcHJlZGljY2nDs24sIHF1ZSBlcyBzaW1wbGVtZW50ZSBsYSBtZWRpYSBkZSBsb3MgdmFsb3JlcyBkZSByZXNwdWVzdGEgcGFyYSBsYXMgb2JzZXJ2YWNpb25lcyBkZSBlbnRyZW5hbWllbnRvIGVuICRSX2okLgoKRWwgb2JqZXRpdm8gZXMgbWluaW1pemFyIGVsIFJTUyAoUmVzaWR1YWwgU3VtIG9mIFNxdWFyZXMpOgoKJCQKXHN1bV97aj0xfV5KXHN1bV97aVxpbiBSX2p9ICh5X2ktXGhhdHt5fV97Ul9qfSleMgokJAoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL3RyZWU1LnBuZyl7d2lkdGg9NTAlfQo8L2NlbnRlcj4KCkRlYmVtb3MgdGVuZXIgYWxndW5hcyBjb25zaWRlcmFjaW9uZXMgZGUgZXN0b3Mgw6FyYm9sZXM6CgotIEVuZm9xdWUgZGVzY2VuZGVudGUgeSBjb2RpY2lvc28gcXVlIHNlIGNvbm9jZSBjb21vIGRpdmlzacOzbiBiaW5hcmlhIHJlY3Vyc2l2YS4KCi0gRXMgZGVzY2VuZGVudGUgcG9ycXVlIGNvbWllbnphIGVuIGxhIHBhcnRlIHN1cGVyaW9yIGRlbCDDoXJib2wgeSBsdWVnbyBkaXZpZGUgc3VjZXNpdmFtZW50ZSBlbCBlc3BhY2lvIGRlIHByZWRpY2Npw7NuCgotIENhZGEgZGl2aXNpw7NuIHNlIGluZGljYSBtZWRpYW50ZSBkb3MgbnVldmFzIHJhbWFzIG3DoXMgYWJham8gZW4gZWwgw6FyYm9sLgoKLSBFcyBjb2RpY2lvc28gcG9ycXVlIGVuIGNhZGEgcGFzbyBkZWwgcHJvY2VzbyBkZSBjb25zdHJ1Y2Npw7NuIGRlbCDDoXJib2wsIHNlIHJlYWxpemEgbGEgbWVqb3IgZGl2aXNpw7NuIGVuIGVzZSBwYXNvIGVuIHBhcnRpY3VsYXIsIGVuIGx1Z2FyIGRlIG1pcmFyIGhhY2lhIGFkZWxhbnRlIHkgZWxlZ2lyIHVuYSBkaXZpc2nDs24gcXVlIGNvbmR1emNhIGEgdW4gbWVqb3Igw6FyYm9sIGVuIGFsZ8O6biBwYXNvIGZ1dHVyby4KCkVuIHJlc3VtZW4gcG9kZW1vcyBzZWd1aXIgbG9zIHNpZ3VpZW50ZXMgcGFzb3M6IAoKMS4gQ29uc2lkZXJhIHRvZG9zIGxvcyBwcmVkaWN0b3JlcyB5IHRvZG9zIGxvcyBwb3NpYmxlcyB2YWxvcmVzIGRlbCBwdW50byBkZSBjb3J0ZQoKMi4gQ2FsY3VsYSBlbCBSU1MgcGFyYSBjYWRhIHBvc2liaWxpZGFkCgozLiBTZWxlY2Npb25hIGxhIHF1ZSB0aWVuZSBtZW5vcyBSU1MKCjQuIENvbnRpbsO6YSBoYXN0YSBxdWUgc2UgYWxjYW56YW4gbG9zIGNyaXRlcmlvcyBkZSBwYXJhZGEKCiMjIFRpcG9zIGRlIMOhcmJvbGVzIGRlIGRlY2lzacOzbgoKMS4gw4FyYm9sIGRlIHJlZ3Jlc2nDs246IFBhcmEgbGEgdmFyaWFibGUgb2JqZXRpdm8gY3VhbnRpdGF0aXZhIGNvbnRpbnVhLiBQb3IgZWplbXBsbywgcHJlZGljY2nDs24gZGUgcHJlY2lwaXRhY2lvbmVzLCBwcmVkaWNjacOzbiBkZSBpbmdyZXNvcywgcHJlZGljY2nDs24gZGUgbm90YXMsIGV0Yy4KCjIuIMOBcmJvbCBkZSBjbGFzaWZpY2FjacOzbjogUGFyYSB2YXJpYWJsZXMgb2JqZXRpdm8gY2F0ZWfDs3JpY2FzIGRpc2NyZXRhcy4gRWouIFByZWRpY2Npw7NuIGRlIGFsdG8gbyBiYWpvLCB2aWN0b3JpYSBvIHDDqXJkaWRhLCBzYWx1ZGFibGUgbyBubyBzYWx1ZGFibGUsIGV0Yy4KCjxjZW50ZXI+CiFbXSguL2ltYWdlcy90cmVlNC5wbmcpe3dpZHRoPTUwJX0KPC9jZW50ZXI+CgoKMS4gRGl2aWRpbW9zIGVsIGVzcGFjaW8gZGVsIHByZWRpY3RvciwgZXMgZGVjaXIsIGVsIGNvbmp1bnRvIGRlIHZhbG9yZXMgcG9zaWJsZXMgcGFyYSAkWF8xLFhfMiwuLi4sWF9wJCwgZW4gJEokIHJlZ2lvbmVzIGRpc3RpbnRhcyB5IG5vIHN1cGVycHVlc3RhcywgJFJfMSxSXzIsLi4uLFJfSiQuCgoyLiBQYXJhIGNhZGEgb2JzZXJ2YWNpw7NuIHF1ZSBjYWUgZW4gbGEgcmVnacOzbiAkUl9qJCwgaGFjZW1vcyBsYSBtaXNtYSBwcmVkaWNjacOzbiwgcXVlIGVzIHNpbXBsZW1lbnRlIGxhIG1lZGlhIGRlIGxvcyB2YWxvcmVzIGRlIHJlc3B1ZXN0YSBwYXJhIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGVudHJlbmFtaWVudG8gZW4gJFJfaiQuCgpFbCBvYmpldGl2byBlcyBtaW5pbWl6YXIgZWwgUlNTIChSZXNpZHVhbCBTdW0gb2YgU3F1YXJlcyk6CgokJApcc3VtX3tqPTF9Xkpcc3VtX3tpXGluIFJfan0gKHlfaS1caGF0e3l9X3tSX2p9KV4yCiQkCgo8Y2VudGVyPgohW10oLi9pbWFnZXMvdHJlZTUucG5nKXt3aWR0aD01MCV9CjwvY2VudGVyPgoKRGViZW1vcyB0ZW5lciBhbGd1bmFzIGNvbnNpZGVyYWNpb25lcyBkZSBlc3RvcyDDoXJib2xlczoKCi0gRW5mb3F1ZSBkZXNjZW5kZW50ZSB5IGNvZGljaW9zbyBxdWUgc2UgY29ub2NlIGNvbW8gZGl2aXNpw7NuIGJpbmFyaWEgcmVjdXJzaXZhLgoKLSBFcyBkZXNjZW5kZW50ZSBwb3JxdWUgY29taWVuemEgZW4gbGEgcGFydGUgc3VwZXJpb3IgZGVsIMOhcmJvbCB5IGx1ZWdvIGRpdmlkZSBzdWNlc2l2YW1lbnRlIGVsIGVzcGFjaW8gZGUgcHJlZGljY2nDs24KCi0gQ2FkYSBkaXZpc2nDs24gc2UgaW5kaWNhIG1lZGlhbnRlIGRvcyBudWV2YXMgcmFtYXMgbcOhcyBhYmFqbyBlbiBlbCDDoXJib2wuCgotIEVzIGNvZGljaW9zbyBwb3JxdWUgZW4gY2FkYSBwYXNvIGRlbCBwcm9jZXNvIGRlIGNvbnN0cnVjY2nDs24gZGVsIMOhcmJvbCwgc2UgcmVhbGl6YSBsYSBtZWpvciBkaXZpc2nDs24gZW4gZXNlIHBhc28gZW4gcGFydGljdWxhciwgZW4gbHVnYXIgZGUgbWlyYXIgaGFjaWEgYWRlbGFudGUgeSBlbGVnaXIgdW5hIGRpdmlzacOzbiBxdWUgY29uZHV6Y2EgYSB1biBtZWpvciDDoXJib2wgZW4gYWxnw7puIHBhc28gZnV0dXJvLgoKRW4gcmVzdW1lbiBwb2RlbW9zIHNlZ3VpciBsb3Mgc2lndWllbnRlcyBwYXNvczogCgoxLiBDb25zaWRlcmEgdG9kb3MgbG9zIHByZWRpY3RvcmVzIHkgdG9kb3MgbG9zIHBvc2libGVzIHZhbG9yZXMgZGVsIHB1bnRvIGRlIGNvcnRlCgoyLiBDYWxjdWxhIGVsIFJTUyBwYXJhIGNhZGEgcG9zaWJpbGlkYWQKCjMuIFNlbGVjY2lvbmEgbGEgcXVlIHRpZW5lIG1lbm9zIFJTUwoKNC4gQ29udGluw7phIGhhc3RhIHF1ZSBzZSBhbGNhbnphbiBsb3MgY3JpdGVyaW9zIGRlIHBhcmFkYQoKIyMgVGlwb3MgZGUgw6FyYm9sZXMgZGUgZGVjaXNpw7NuCgoxLiDDgXJib2wgZGUgcmVncmVzacOzbjogUGFyYSBsYSB2YXJpYWJsZSBvYmpldGl2byBjdWFudGl0YXRpdmEgY29udGludWEuIFBvciBlamVtcGxvLCBwcmVkaWNjacOzbiBkZSBwcmVjaXBpdGFjaW9uZXMsIHByZWRpY2Npw7NuIGRlIGluZ3Jlc29zLCBwcmVkaWNjacOzbiBkZSBub3RhcywgZXRjLgoKMi4gw4FyYm9sIGRlIGNsYXNpZmljYWNpw7NuOiBQYXJhIHZhcmlhYmxlcyBvYmpldGl2byBjYXRlZ8OzcmljYXMgZGlzY3JldGFzLiBFai4gUHJlZGljY2nDs24gZGUgYWx0byBvIGJham8sIHZpY3RvcmlhIG8gcMOpcmRpZGEsIHNhbHVkYWJsZSBvIG5vIHNhbHVkYWJsZSwgZXRjLgoKIyMgQ3JpdGVyaW9zIGRlIHBhcmFkYQoKUG9kZW1vcyB0ZW5lciBkaXN0aW50b3MgY3JpdGVyaW9zLCB0YWxlcyBjb21vOgoKIDEuIE9ic2VydmFjaW9uZXMgbcOtbmltYXMgZW4gdW4gbm9kby4gIE7Dum1lcm8gbcOtbmltbyBkZSBvYnNlcnZhY2lvbmVzIG5lY2VzYXJpYXMgcGFyYSB1bmEgbnVldmEgZGl2aXNpw7NuLgogCjIuIE9ic2VydmFjaW9uZXMgbcOtbmltYXMgZW4gZWwgbm9kbyBkZSBsYSBob2phLiBOw7ptZXJvIG3DrW5pbW8gZGUgb2JzZXJ2YWNpb25lcyBuZWNlc2FyaWFzIGVuIGNhZGEgbm9kbyBkZXNwdcOpcyBkZSBsYSBkaXZpc2nDs24uCgozLiBQcm9mdW5kaWRhZCBtw6F4aW1hLiBOw7ptZXJvIG3DoXhpbW8gZGUgY2FwYXMgZGVsIMOhcmJvbCBwb3NpYmxlcwoKCiMgQ29uc3RydXlhbW9zIHVuIMOhcmJvbCBkZSBkZWNpc2nDs24gZW4gUgoKIyMgRGF0b3MgcGFyYSBlbCBlamVtcGxvCgpQYXJhIHZlciBjw7NtbyBjb25zdHJ1aW1vcyB1biDDoXJib2wsIHByaW1lcm8gcXVlIHRvZG8gdmFtb3MgYSB2ZXIgbnVlc3Ryb3MgZGF0b3M6CgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCm1vdmllIDwtIHJlYWQuY3N2KCJNb3ZpZV9yZWdyZXNzaW9uLmNzdiIpCmhlYWQobW92aWUpCmNvbG5hbWVzKG1vdmllKQpgYGAKClJlY29yZGVtb3MgdW4gcG9jbyBkZSBleHBsb3JhY2nDs24gZGUgZGF0b3MgdXNhbmRvIGVsIHBhcXVldGUgYERhdGFFeHBsb3JlcmA6CmBgYHtyfQpsaWJyYXJ5KERhdGFFeHBsb3JlcikKcGxvdF9pbnRybyhtb3ZpZSkKcGxvdF9taXNzaW5nKG1vdmllKQoKYGBgCgpFbiBlc3RhIHByaW1lcmEgcGFydGUgcG9kZW1vcyB2ZXIgY8OzbW8gbG9zIGRhdG9zIHF1ZSB2YW1vcyBhIGFuYWxpemFyIG5vIHRpZW5lbiBwcm9ibGVtYXMgZGUgZGF0b3MgZmFsdGFudGVzLCBwYXJhIGxvIGN1YWwgcHVzaWVyYW1vcyBlbGltaW5hciBhbGd1bmEgZGUgbGFzIHZhcmlhYmxlcy4KClBhcmEgbWlyYXIgdW4gcG9jbyBkZSBxdcOpIHRpcG9zIGRlIHZhcmlhYmxlcyB0ZW5lbW9zIGVuIG51ZXN0cm8gZGF0YXNldCBoYWdhbW9zIHVuIHJlc3VtZW4gZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlczoKCmBgYHtyfQpzdW1tYXJ5KG1vdmllKQpgYGAKCiMjIENvbnN0cnVjY2nDs24gZGUgY29uanVudG9zIGRlIGRhdG9zIHRlc3QtdHJhaW4KClBhcmEgcmVhbGl6YXIgZXN0YSBwYXJ0aWNpw7NuLCB1dGlsaXphcmVtb3Mgb3RyYSBtZXRvbG9nw61hIHVzYW5kbyBlbCBwYXF1ZXRlIGBjYVRvb2xzYCwgcG9yIGxvIGN1YWwgcHJpbWVybyBkZWJlbW9zIGRlIGluc3RhbGFybG8gZW4gUiBwb3IgbWVkaW8gZGVsIGNvbWFuZG8gYGluc3RhbGwucGFja2FnZXMoImNhVG9vbHMiKWAuIFlhIHRlbmllbmRvIGxhIGluc3RhbGFjacOzbiBwcm9jZWRlbW9zIGEgY2FyZ2FyIGxhIGxpYnJlcsOtYSB5IGEgcmVhbGl6YXIgbnVlc3RhIHBhcnRpY2nDs246CgpgYGB7cn0KbGlicmFyeShjYVRvb2xzKQoKc2V0LnNlZWQoMCkgICMgTGEgc2VtaWxsYQpzcGxpdCA9IHNhbXBsZS5zcGxpdChtb3ZpZSxTcGxpdFJhdGlvPTAuOCkgIyBMb3MgZGF0b3MgZGUgdHJhaW4gc2Vyw6FuIGRlIDgwJQp0cmFpbiA9IHN1YnNldChtb3ZpZSxzcGxpdCA9PSBUUlVFKQp0ZXN0ID0gc3Vic2V0KG1vdmllLHNwbGl0ID09IEZBTFNFKQoKYGBgCgpMYSB2YXJpYWJsZSBkZSBjb250cm9sIHBhcmEgZW5jb25ldHJhciBlbCDDoXJib2wgZGUgZGVjaXNpw7NuIGVzIGBDb2xsZWN0aW9uYCwgcGFyYSBsbyBjdWFsLCByZWNvcmRlbW9zIHF1ZSBlcyBpbXBvcnRhbnRlIHZlciBxdWUgZXN0YSB2YXJpYWJsZSBzZSBkaXN0cmlidXlhIGRlIGZvcm1hIHNpbWlsYXIgZW4gYW1ib3MgY29uanVudG9zIGRlIHRyYWluIHkgdGVzdDoKCmBgYHtyfQpzdW1tYXJ5KHRyYWluJENvbGxlY3Rpb24pCnN1bW1hcnkodGVzdCRDb2xsZWN0aW9uKQpgYGAKCiMjIENvbnN0cnVjY2nDs24gZGVsIMOhcmJvbCBkZSBkZWNpc2nDs24gcGFyYSBwcm9ibGVtYSBkZSByZWdyZXNpw7NuCgpQYXJhIGxhIGNvbnN0cnVjY2nDs24gZGUgbnVlc3RybyDDoXJib2wgZGUgZGVjaXNpw7NuIHBhcmEgdW4gcHJvYmxlbWEgZGUgcmVncmVzacOzbiwgbm9zIGFwb3lhcmVtb3MgZGUgbG9zIHBhcXVldGUgZGUgUjogYHJwYXJ0YCwgYHJwYXJ0LnBsb3RgLiAKCjxjZW50ZXI+CiFbXSguL2ltYWdlcy9ycGFydC5wbmcpe3dpZHRoPTE1JX0KPC9jZW50ZXI+CgpMbyBwcmltZXJvIHF1ZSBoYXJlbW9zIGVzIGNhcmdhciBsYXMgZG9zIGxpYnJlcsOtYXMgbHVlZ28gZGUgaGFiZXIgaW5zdGFsYWRvIHByZXZpYW1lbnRlIGxvcyBwYXF1ZXRlczoKYGBge3J9CmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKYGBgCgpQYXJhIGVzdGUgZWplbXBsbywgdXRpbGl6YXJlbW9zIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIHF1ZSBzZSBlbmN1ZW50cmEgZW4gZXN0YSBwYXF1ZXRlcsOtYSwgZG9uZGUgbnVlc3RyYSB2YXJpYWJsZSBkZSBpbnRlcsOpcyBvIHZhcmlhYmxlIG9iamV0aXZvIHNlcsOhIGBDb2xlY3Rpb25gLiAgRWwgY3JpdGVyaW8gZGUgcGFyYWRhIGxvIHRvbWFyZW1vcyBwb3IgbWVkaW8gZGUgbGEgZnVuY2nDs24gYHJwYXJ0LmNvbnRyb2xgLCBkb25kZSBzZSBlc3BlY2lmaWNhIHF1ZSBgbWF4ZGVwdGggPSAgM2AsIGVzdG8gZXN0YWJsZWNlIHF1ZSwgbGEgcHJvZnVuZGlkYWQgbcOheGltYSBkZSB1biBub2RvIGZpbmFsIGRlbCDDoXJib2wsIGNvbnRhbmRvIGRlc2RlIGVsIG5vZG8gcmHDrXogZXMgbcOheGltbyAzLgoKYGBge3J9CiMgTW9kZWxvIGRlIHJlZ3Jlc2nDs24gY29uIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHRyYWluCnJlZ3RyZWUgPC0gcnBhcnQoZm9ybXVsYT1Db2xsZWN0aW9ufi4sIAogICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbiwgCiAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWF4ZGVwdGggPSAgMykpCgojIFZpc3VhbGl6YWNpw7NuIGRlbCDDoXJib2wgZGUgZGVjaXNpw7NuCnJwYXJ0LnBsb3QocmVndHJlZSwgYm94LnBhbGV0dGUgPSAiUmRCdSIsIGRpZ2l0cz0gLTMpCmBgYAoKCmBgYHtyfQojIEPDoWxjdWxvIGRlbCBwcmVkaWN0b3IKdGVzdCA8LSB0ZXN0ICU+JSAgbXV0YXRlKHByZWQgPXByZWRpY3QocmVndHJlZSwgdGVzdCwgdHlwZSA9ICJ2ZWN0b3IiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHJlZyA9IDE6bnJvdyh0ZXN0KSwKICAgICAgICAgICAgICAgICAgICAgICAgIGRpZl9wcmVkX2NvbGwgPSBDb2xsZWN0aW9uLXByZWQpCgpsaWJyYXJ5KGdncGxvdDIpCgpnZ3Bsb3QodGVzdCkrCiAgZ2VvbV9wb2ludChhZXMoeD1yZWcsIHk9ZGlmX3ByZWRfY29sbCkpCgpNU0UyIDwtIG1lYW4oKHRlc3QkcHJlZCAtIHRlc3QkQ29sbGVjdGlvbileMikKYGBgCgojIyBQb2RhIGRlIHVuIMOhcmJvbCBkZSBkZWNpc2nDs24gCgpMb3MgZ3JhbmRlcyDDoXJib2xlcyBkZSBkZWNpc2nDs24gKGNvbiBtdWNob3Mgbm9kb3MgeSBtdWNoYXMgZGl2aXNpb25lcyksIHRpZW5lbiBkb3MgcHJvYmxlbWFzOiBsYSBwcmltZXJhIGVzIHF1ZSBzb24gZGlmw61jaWxlcyBkZSBpbnRlcnByZXRhciB5IGxhIHNlZ3VuZGEgZXMgcXVlIHNvYnJlYWxpbWVudGFuIGxvcyBkYXRvcyBkZWwgdHJlbiBkYW5kbyBhc8OtIHVuIG1hbCByZW5kaW1pZW50byBkZSBsb3MgYWN0aXZvcy4gVW5hIGRlIGxhcyBmb3JtYXMgZGUgY29udHJvbGFyIGVzdG8sIGVzIGNvbW8geWEgbG8gaGVtb3MgZXN0YWRvIGhhY2llbmRvOiBwb3IgbWVkaW8gZGUgbG9zIGNyaXRlcmlvcyBkZSBwYXJhZGEuCgpPdHJhIGVzdHJhdGVnaWEgcXVlIHBvZGVtb3MgdGVuZXIgZXMgaGFjZXIgdW4gw6FyYm9sIG11eSBncmFuZGUsIHkgbHVlZ28gcG9kYXJsbyBvIGNvcnRhciBhbGd1bmFzIHBhcnRlcyBxdWUgbm8gbm9zIGJlbmVmaWNpYW4gZW4gZWwgY8OhbGN1bG8gZGVsIG9iamV0aXZvLgoKQXPDrSBxdWUgYWhvcmEgcXVlcmVtb3MgdW4gc3Viw6FyYm9sIGNvbiBlcyBsYSBwcnVlYmEgbcOhcyBiYWphIGEgdW5hIHRhc2EgcG9kZW1vcyB1dGlsaXphciBsYSB2YWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgYXZlcmlndWFyIGxhIHRhc2EgZGUgZXJyb3IgZGUgbGEgcHJ1ZWJhIGRlIHRvZG9zIGxvcyBzdWJzaWRpb3MgZGUgZXN0ZSB0aXBvLCBwZXJvIGVzdG8gcHVlZGUgc2VyIGNvbXB1dGFjaW9uYWxtZW50ZSBjYXJvICh2YSBhIHRhcmRhciBtdWNobyB0aWVtcG8pLgoKUG9yIGxvIHRhbnRvLCB1dGlsaXphbW9zIHVuIG3DqXRvZG8gbGxhbWFkbyAicG9kYSBkZSBjb21wbGVqaWRhZCBkZSBjb3N0ZXMiIG8gInBvZGEgZGUgZXNsYWJvbmVzIG3DoXMgZGVzcGllcnRvcyIgZW4gbGEgbWF0ZXJpYS4KCkHDsWFkaW1vcyBlbCBjb3N0ZSBhZGljaW9uYWwgZGVsIG7Dum1lcm8gZGUgbm9kb3MgdGVybWluYWxlcyBlbiBlbCDDoXJib2wgYWwgUlNTLCBkZSBtb2RvIHF1ZSBlbiBsdWdhciBkZSBtaW5pbWl6YXIgbGFzIGRpcmVjY2lvbmVzLCBtaW5pbWl6YW1vcyBlbCBSU1MgbcOhcyBlbCBuw7ptZXJvIGRlIG5vZG9zIHRlcm1pbmFsZXMsIGxvIHF1ZSBzZSBkZW5vbWluYSBwYXLDoW1ldHJvIMO6bmljbyBvIHBhcsOhbWV0cm8gZGUgY29tcGxlamlkYWQuCgokJApcc3VtX3ttPTF9Xnt8VHx9XHN1bV97eF9pXGluIFJfbX0oeV9pLVxoYXR7eX1fe1JfbX0pXjIrXGFscGhhfFR8CiQkCgpkb25kZSAkXGFscGhhJCBlcyBlbCBwYXLDoW1ldHJvIGRlIGNvbXBsZWppZGFkLCAkVCQgZXMgZWwgbsO6bWVybyBkZSBob2phcyBkZWwgw6FyYm9sLgoKRXN0YW1vcyBtaW5pbWl6YW5kbyBlbCBzaXN0ZW1hIGRlIG9yZGVuIG5vcm1hbCBwb3IgbG8gcXVlIG9idGVuZW1vcyByYcOtY2VzIG5vcm1hbGVzIGRlbCDDoXJib2wgcGVybyBlbCB2YWxvciBkZSBsYSBtdWx0YSBhdW1lbnRhIHF1ZSBlcyBlbCBhdW1lbnRvIGRlIGxhIHBlbmEgcG9yIHRlbmVyIG3DoXMgZGl2aXNpw7NuLgoKUG9yIGxvIHRhbnRvLCBlbCB2YWxvciBkZSAkXGFscGhhJCBjb250cm9sYSBlbCBjcmVjaW1pZW50byBkZWwgw6FyYm9sLCBhc8OtIHF1ZSBlbCBwcm9jZXNvIGVzIGVsIHNpZ3VpZW50ZTogdGVuZW1vcyBxdWUgZW5jb250cmFyIGVsIHZhbG9yIGRlICRcYWxwaGEkIGVuIGVsIHF1ZSBvYnRlbmVtb3MgZWwgbcOtbmltbyBlcnJvciBjb24gdmFsaWRhY2nDs24gY3J1emFkYSBlbiBudWVzdHJvIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8sIHV0aWxpemFuZG8gZWwgdmFsb3IgZGUgJFxhbHBoYSQgcGFyYSBwb2RhciBlbCDDoXJib2wgeSBlc3BlcmFtb3MgcXVlIGVzdGUgw6FyYm9sIHBvZGFkbyBzZSBkZXNlbXBlw7FlIG1lam9yIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYS4KCkhhZ2Ftb3MgZXN0ZSBwcm9jZWRpbWllbnRvIHNpZ3VpZW5kbyBlbCBlamVtcGxvIHF1ZSBhbnRlcmlvciBkZSBsYXMgcGVsw61jdWxhcyBlbiBSLiAgUHJpbWVybyBoYWdhbW9zIGVsIMOhcmJvbCBjb21wbGV0byAoY29uIHRvZGFzIGxhcyBwb3NpYmlsaWRhZGVzKSwgZWwgY3VhbCBsbGFtYXJlbW9zIGBmdWxsdHJlZWAgeSBkb25kZSBgY3BgIGVzIGNvbnRyb2wgcGFyYW1ldGVyIC4gCgpgYGB7cn0KIyBUcmVlIFBydW5pbmcKZnVsbHRyZWUgPC0gcnBhcnQoZm9ybXVsYT1Db2xsZWN0aW9ufi4sIAogICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbiwgCiAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSAwKSkKCiMgVmlzdWFsaXphY2nDs24gZGVsIMOhcmJvbCBkZSBkZWNpc2nDs24KcnBhcnQucGxvdChmdWxsdHJlZSwgYm94LnBhbGV0dGUgPSAiUmRCdSIsIGRpZ2l0cz0gLTMpCmBgYAoKUG9kZW1vcyB2ZXIgZWwgY29tcG9ydGFtaWVudG8gZGUgw6lzdGUgcGFyw6FtZXRybyBkZSBjb250cm9sIHBvciBtZWRpbyBsYSBmdW5jacOzbiBgcHJpbnRjcCgpYApgYGB7cn0KcHJpbnRjcChmdWxsdHJlZSkKYGBgCgpBcXXDrSBwdWVkZXMgdmVyIGxvcyBkaWZlcmVudGVzIHZhbG9yZXMgZGUgQ1AgcGFyYSBsb3MgY3VhbGVzIG9idGVuZW1vcyBkaWZlcmVudGVzIHZhbG9yZXMgZGUgZXJyb3IgcmVsYXRpdm8uIEVsIHZhbG9yIGRlIGB4ZXJyb3JgIGVzIGVsIGVycm9yIGRlIHZhbGlkYWNpw7NuIGNydXphZGEuIAoKKioqUXVlcmVtb3MgZW5jb250cmFyIGVzZSB2YWxvciBkZSBDUCBwYXJhIGVsIGN1YWwgbGEgZWRpY2nDs24gZGUgdmFsaWRhY2nDs24gY3J1emFkYSBlcyBtw61uaW1hKioqLiBFc2UgdmFsb3IgZGUgQ1Agb3RybyBwYXLDoW1ldHJvIGRlIGFqdXN0ZSBzZSB1dGlsaXphcsOhIHBhcmEgcG9kYXIgZWwgw6FyYm9sLgoKQWhvcmEgdGFtYmnDqW4gc2UgcHVlZGUgZ3JhZmljYXIgZXN0b3MgdmFsb3JlcyBkZSBDUCB5IGV4ZXRlciB1c2FuZG8gZXN0YSBmdW5jacOzbiBkZSBgcGxvdGNwYCB5IHNlIHB1ZWRlIHZlciBxdWUgYSBtZWRpZGEgcXVlIHNlZ3VpbW9zIGluY3JlbWVudGFuZG8gZWwgdmFsb3IgZGUgQ1AgZWwgZXJyb3IgcmVsYXRpdm8gZXN0w6EgaW5pY2lhbG1lbnRlIGRpc21pbnV5ZW5kbyB5IGx1ZWdvIGNvbWllbnphIGEgYXVtZW50YXIgeSBhc8OtIHN1Y2VzaXZhbWVudGUuIFBvciBsbyB0YW50bywgY29tbyBzdWJlIGUgYmFqYSBlc3RlIHZhbG9yLCBlbiBhbGfDum4gbHVnYXIgZGUgaGFiYXIgdW4gdmFsb3IgcGFyYSBDUCBwYXJhIGVsIGN1YWwgZXMgbcOtbmltbyAobcOtbmltbyBnbG9iYWwpLgoKYGBge3J9CnBsb3RjcChmdWxsdHJlZSkKYGBgCgpFbmNvbnRyZW1vcyBlbCBtw61uaW1vIGNhbG9yIGRlIENQLiBjb24gbGEgc2lndWllbnRlIGzDrW5lYSB2YW1vcyBhIGVuY29udHJhciBlbCB2YWxvciBkZSBDUCBwYXJhIGVsIGN1YWwgZXMgZWwgZXJyb3IgZGUgdmFsaWRhY2nDs24gY3J1emFkYSBgeGVycm9yYCBlcyBtw61uaW1vLiAKCmBgYHtyfQptaW5jcCA8LSByZWd0cmVlJGNwdGFibGVbd2hpY2gubWluKHJlZ3RyZWUkY3B0YWJsZVssInhlcnJvciJdKSwiQ1AiXQptaW5jcApgYGAKClVzYW5kbyBlc3RlIHBhcsOhbWV0cm8gcXVlIGVuY29udHJhbW9zIGNvbW8gbcOtbmltbywgIGVqZWN1dGFyZW1vcyBsYSBmdW5jacOzbiBgcHJ1bmUoKWAgeSBlbCBtaXNtbyDDoXJib2wgc2Vyw6EgcG9kYWRvLgoKYGBge3J9CnBydW5lZHRyZWUgPC0gcHJ1bmUoZnVsbHRyZWUsIGNwID0gbWluY3ApCnJwYXJ0LnBsb3QocHJ1bmVkdHJlZSwgYm94LnBhbGV0dGUgPSAiUmRCdSIsIGRpZ2l0cyA9IDMpCmBgYAoKUG9kZW1vcyBjb21wYXJhciBsb3MgcmVzdWx0YWRvcyBjb24gZWwgw6FyYm9sIHNpbiBwb2RhciB5IGVsIMOhcmJvbCBwb2RhZG8sIHBhcmEgZXN0byBoYWNlbW9zIGVsIGPDoWxjdWxvLCB0YWwgY29tbyBsbyBoaWNpbW9zIGVuIGVsIGVqZW1wbG8gYW50ZXJpb3IsIGFudGVzIGRlIHBvZGFyOgoKYGBge3J9CnRlc3QkZnVsbHRyZWUgPC0gcHJlZGljdChmdWxsdHJlZSwgdGVzdCwgdHlwZSA9ICJ2ZWN0b3IiKQpNU0UyIDwtIG1lYW4oKHRlc3QkZnVsbHRyZWUgLSB0ZXN0JENvbGxlY3Rpb24pXjIpCk1TRTIKCnRlc3QkcHJ1bmVkIDwtIHByZWRpY3QocHJ1bmVkdHJlZSwgdGVzdCwgdHlwZSA9ICJ2ZWN0b3IiKQpNU0UycHJ1bmVkIDwtIG1lYW4oKHRlc3QkcHJ1bmVkIC0gdGVzdCRDb2xsZWN0aW9uKV4yKQpNU0UycHJ1bmVkCmBgYAoKIyMgQ29uc3RydXlhbW9zIHVuIMOhcmJvbCBkZSBjbGFzaWZpY2FjacOzbgoKUGFyYSBsb3Mgw6FyYm9sZXMgZGUgY2xhc2lmaWNhY2nDs24sIHV0aWxpemFtb3MgZWwgbW9kbywgZG9uZGUsIGxhIGNhdGVnb3LDrWEgbcOhcyBmcmVjdWVudGUgZW4KZXNhIHJlZ2nDs24gc2Vyw6EgbGEgcHJlZGljY2nDs24uCgpUYW50byBsYSByZWdyZXNpw7NuIGNvbW8gbGEgY2xhc2lmaWNhY2nDs24gdXRpbGl6YW4gbGEgZGl2aXNpw7NuIGJpbmFyaWEgcmVjdXJzaXZhCgpFbiBsYSByZWdyZXNpw7NuIHNlIHV0aWxpemEgZWwgUlNTIHBhcmEgZGVjaWRpciBsYSBkaXZpc2nDs24KCkVuIGxhIGNsYXNpZmljYWNpw7NuIHBvZGVtb3MgdXRpbGl6YXIKCjEuIFRhc2EgZGUgZXJyb3IgZGUgY2xhc2lmaWNhY2nDs24KCjIuIMONbmRpY2UgZGUgR2luaQoKMy4gRW50cm9ww61hIGNydXphZGEgCgpBaG9yYSBwYXJhIHZlcmxvIGVuIHVuIGVqZW1wbG8gdXRpbGl6YXJlbW9zIGVsIG1pc21vIGRhdGFzZXQgZGUgbGFzIHBlbMOtY3VsYXMsIHNvbG8gcXVlIGFob3JhIHF1ZXJlbW9zIHZlciBjbGFzaWZpY2FyIGxhcyBwZWzDrWN1bGFzIGRlIGFjdWVyZG8gc2kgZ2FuYXLDoW4gdW4gT3NjYXIgcG9yIG1lZGlvIGRlIGxhIHZhcmlhYmxlIGJpbmFyaWEgYFN0YXJfVGVjaF9Pc2NhcmAKCkFob3JhIHZlYW1vcyBjw7NtbyByZWFsaXphbW9zIHVuIMOhcmJvbCBkZSBjbGFzaWZpY2FjacOzbiBkZXNkZSBSLiAKCmBgYHtyfQptYyA8LSByZWFkLmNzdigiTW92aWVfY2xhc3NpZmljYXRpb24uY3N2IikKaGVhZChtYykKc3VtbWFyeShtYykKYGBgClBvZGVtb3MgdmVyIHF1ZSBsYSB2YXJpYWJsZSBgVGltZV90YWtlbmAgdGllbmUgdmFsb3JlcyBlbiBOQSwgZXN0byBub3MgdmEgYSBnZW5lcmFyIHByb2JsZW1hcyBlbiBlbCBtb21lbnRvIHF1ZSByZWFsaWNlbW9zIGxhcyBjb3JyaWRhcyBkZSBjdWFscXVpZXIgbW9kZWxvIGVuIFIuIFBhcmEgZWxpbWluYXIgZXN0b3MgTkEgcmVhbGl6YW1vcyBlbCBzaWd1aWVudGUgcHJvY2VkaW1pZW50by4KCmBgYHtyfQptYyRUaW1lX3Rha2VuW2lzLm5hKG1jJFRpbWVfdGFrZW4pXSA8LSBtZWFuKG1jJFRpbWVfdGFrZW4sbmEucm0gPSBUUlVFKQpgYGAKCllhIHF1ZSBoZW1vcyBsaW1waWFkbyBsb3MgZGF0b3MsIHByb2NlZGVtb3MgYSByZWFsaXphciBsYSBzZXBhcmFjacOzbiBkZSBudWVzdHJvcyBjb25qdW50b3MgVGVzdCB5IFRyYWluLCBzaW4gb2x2aWRhciBxdWUgc2ksIG5vIGhlbW9zIGVqZWN1dGFkbyBsYSBsaWJyZXLDrWEgYGNhVG9vbHNgIGRlYmVtb3MgZGUgaGFjZXJsbyBwb3IgbWVkaW8gZGUgbCBjb21hbmRvIGBsaWJyYXJ5KGNhVG9vbHMpYDoKCmBgYHtyfQpzZXQuc2VlZCgwKQpzcGxpdCA8LSBzYW1wbGUuc3BsaXQobWMsU3BsaXRSYXRpbyA9IDAuOCkKdHJhaW5jIDwtIHN1YnNldChtYyxzcGxpdCA9PSBUUlVFKQp0ZXN0YyA8LSBzdWJzZXQobWMsc3BsaXQgPT0gRkFMU0UpCmBgYAoKQWhvcmEsIHV0aWxpemFuZG8gZWwgcGFxdWV0ZSBkZSBgcnBhcnRgLCB2YW1vcyBhIGVuY29udHJhciBlbCDDoXJib2wgZGUgY2xhc2lmaWNhY2nDs24sIGNhbWJpYW5kbyBlbCBtw6l0b2RvIHBvciBgY2xhc3NgLgoKYGBge3J9CiMgTW9kZWxvIGRlIGNsYXNpZmljYWNpw7NuCmNsYXNzdHJlZSA8LSBycGFydChmb3JtdWxhID0gU3RhcnRfVGVjaF9Pc2Nhcn4uLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluYywKICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjbGFzcyIsCiAgICAgICAgICAgICAgICAgICBjb250cm9sID0gcnBhcnQuY29udHJvbChtYXhkZXB0aCA9IDMpKQoKcnBhcnQucGxvdChjbGFzc3RyZWUsIGJveC5wYWxldHRlPSJSZEJ1IiwgZGlnaXRzID0gLTMpCmBgYAoKRGUgaWd1YWwgZm9ybWEsIHBvZGVtb3MgYnVzY2FyIGVsIHByZWRpY3RvciwgZXZhbHVhbmRvIGVuIG51ZXN0cm8gbW9kZWxvIGVuIGxvcyBkYXRvcyBkZSB0ZXN0OgoKYGBge3J9CnRlc3RjJHByZWQgPC0gcHJlZGljdChjbGFzc3RyZWUsIHRlc3RjLCB0eXBlPSJjbGFzcyIpIAp0ZXN0YyAlPiUgc2VsZWN0KFN0YXJ0X1RlY2hfT3NjYXIsCiAgICAgICAgICAgICAgICAgcHJlZCkKYGBgCgpQb2RlbW9zIHZlciB5IGNvbXBhcmFyIGPDs21vIGVzdMOhIGNsYXNpZmljYW5kbyBudWVzdHJvIG1vZGVsbyB5IHZlbW9zIHF1ZSBzb2xvIGVuIGVzdGEgc2ltcGxlIHZpc3VhbGl6YWNpw7NuIG5vIHRlbmVtb3MgdG9kb3MgbG9zIGRhdG9zIGRlIGNsYXNpZmljYWRvcyBkZSBmb3JtYSBjb3JyZWN0YS4KClBhcmEgY29tcGFyYXIgY29tbyBzZSBlc3TDoSBjb21wb3J0YW5kbyBlbiBmb3JtYSBnZW5lcmFsIG51ZXN0cm8gbW9kZWxvcyBkZSBjbGFzaWZpY2FjacOzbiBwb3IgbWVkaW8gZGUgdW4gw6FyYm9sIGRlIGRlY2lzacOzbiwgdW5hIGZvcm1hIGVzIGNvbXBhcmFyIGdyw6FmaWNhbWVudGUgbG9zIHZhbG9yZXMgYWN0dWFsZXMganVudG8gY29uIGxvcyBxdWUgc2UgcHJlZGljZW4uCgpIYWdhbW9zIHVuYSBjb21wYXJhY2nDs24gY3JlYW5kbyB1bmEgdGFibGE6CmBgYHtyfQp0YWJsZSh0ZXN0YyRTdGFydF9UZWNoX09zY2FyLHRlc3RjJHByZWQpCmBgYAoKRXN0YSBtYXRyaXogKCoqKmxhIGZhbW9zYSBtYXRyaXogZGUgY29uZnVzacOzbioqKiksIGxvIHF1ZSBub3MgaW5kaWNhIGVzIHF1ZSBwYXJhIGVsIHByaW1lciBjYXNvLCBsYSBkaWFnb25hbCBub3MgbW9zdHJhcsOhIGVsIG7Dum1lcm8gZGUgdmFsb3JlcyBxdWUgc2UgcHJlZGlqZXJvbiBjb3JyZWN0YW1lbnRlLiAgUG9yIGVqZW1wbG8gbGFzIHBlbMOtY3VsYXMgcXVlIG5vIHR1dmllcm9uIE9zY2FyIChgU3RhcnRfVGVjaF9Pc2Nhcj09IDBgKSwgZWwgbW9kZWxvICBlbmNvbnRyw7MgNDUgY29ycmVjdGFzIGRlIDUwICg0NSs1KSwgbWllbnRyYXMgcXVlIHBhcmEgbGFzIHBlbMOtY3VsYXMgcXVlIHR1dmllcm9uIE9zY2FyLCBlbCBtb2RlbG8gZW5jb250csOzIDE5IGNvcnJlY3RhcyBkZSA1NyAoMzgrMTkpLgoKQXPDrSBxdWUgY3VhbmRvIGxhIHBlbMOtY3VsYSBlcyByZWFsbWVudGUgZ2FuYXIgZWwgT3NjYXIgdGVuZW1vcyB1bmEgbXV5IG1hbGEgcHJlY2lzacOzbiBkZSBwcmVkaWNjacOzbiAoYWNjdXJhY3kpLiBNaWVudHJhcyBxdWUgY3VhbmRvIGxhIHBlbMOtY3VsYSBubyBnYW5hIGVsIE9zY2FyIHRlbmVtb3MgdW5hIG11eSBidWVuYSBwcmVjaXNpw7NuIGRlIHByZWRpY2Npw7NuLgoKU2kgcXVlcmVtb3Mgc2FiZXIgY3VhbnRvIGV4YWN0YW1lbnRlIGVzIG51ZXN0cm8gYWNjdXJhY3kgZW4gJSBwb2RlbW9zIHJlYWxpemFybG8gcG9yIG1lZGlvIGxhIGRpdmlzacOzbjogbG9zIHF1ZSBhY2VydMOzL3RvZG9zOgoKYGBge3J9Cig0NSsxOSkvKDQ1KzUrMzgrMTkpKjEwMApgYGAKCiMgUmVmZXJlbmNpYXMKCi0gaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3JwYXJ0L3ZlcnNpb25zLzQuMS0xNQoKLSBodHRwczovL3JwdWJzLmNvbS9qYm9zY29tZW5kb3phL2FyYm9sZXNfZGVjaXNpb25fY2xhc2lmaWNhY2lvbgoKLSBEZWNpc2lvbiBUcmVlcywgUmFuZG9tIEZvcmVzdHMsIEJhZ2dpbmcgJiBYR0Jvb3N0OiBSIFN0dWRpbywgdWRlbXku